/**
 * 
 */
package edu.pw.treegrid.server.service;

import java.io.File;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import xalan.xml.DeserializationTemplate;
import xalan.xml.SerializationTemplate;
import edu.pw.treegrid.server.classmodel.DomainAttribute;
import edu.pw.treegrid.server.classmodel.DomainClass;
import edu.pw.treegrid.server.reportmodel.Report;

/**
 * Klasa odpowiedzialna za serializację modelu biznesowego oraz modelu raportów
 * do plików w formacie XML.
 * 
 * @author Xalan
 */
public class XMLMarshaller {
	private static final String CLASSES_ELEMENT = "classes";
	
	private List<DomainClass> classes;
	private List<DomainAttribute> attributes;
	private Map<String, DomainClass> idToClass = new HashMap<String, DomainClass>();
	private Map<String, DomainAttribute> idToAttribute = new HashMap<String, DomainAttribute>();
	private Map<String, Report> idToReport = new HashMap<String, Report>();
	private Map<String, Long> idToFileLastModified = new HashMap<String, Long>();
	private Long classesLastModified;
	
	private File repositoryDirectoryFile;
	private File classesFile;
	private File reportsDirectory;
	
	public void setRepositoryDirectory(String repositoryDirectory) {
		repositoryDirectoryFile = new File(repositoryDirectory);
		repositoryDirectoryFile.mkdirs();
		reportsDirectory = new File(repositoryDirectory, "reports");
		reportsDirectory.mkdirs();
		classesFile = new File(repositoryDirectory, "classes.xml");
	}

	public List<DomainClass> getClasses() {
		return classes;
	}

	public void setClasses(List<DomainClass> classes) {
		this.classes = classes;
	}

	public DomainClass getClassById(String id) {
		return this.idToClass.get(id);
	}
	
	public DomainAttribute getAttributeById(String id) {
		return this.idToAttribute.get(id);
	}
	
	public void serializeReport(final Report report) {
		
		SerializationTemplate template = new SerializationTemplate() {
			@Override
			protected Element buildRootElement(Document document) {
				return report.serializeToXML(document);
			}
		};
		
		template.serializeToXml(getReportFileName(report.getId()));
	}
	
	public List<Report> deserializeAllReports() {
		this.idToReport.clear();
		
		List<Report> reports = new LinkedList<Report>();
		for(String f: this.reportsDirectory.list()) {
			int lastIndex = f.lastIndexOf(".xml");
			if(lastIndex != -1) {
				String reportId = f.substring(0, lastIndex);
				reports.add(deserializeReport(reportId));
			}
		}
		return reports;
	}
	
	public Report deserializeReport(String reportId) {
		deserializeClasses();
		
		String fileName = getReportFileName(reportId);
		
		// check for file modification time.
		File reportFile = new File(fileName);
		Long lastModified = reportFile.lastModified();
		reportFile = null;
		
		if(this.idToReport.containsKey(reportId) && 
				idToFileLastModified.get(reportId) != null && 
				idToFileLastModified.get(reportId).longValue() == lastModified.longValue()) {
			// use existing report
			//System.out.println("Using existing report " + reportId + " (" + fileName + ").");
			return this.idToReport.get(reportId);
		}else{
			System.out.println("edu.pw.treegrid.server.service.XMLMarshaller: Reloading report " + reportId + " (" + fileName + ").");
			//If the file has been modified or not loaded at all.
			final Report report = new Report();
			
			DeserializationTemplate template = new DeserializationTemplate() {
				@Override
				protected void deserializeFromXML(Document document) {
					report.deserializeFromXML(document.getDocumentElement(), XMLMarshaller.this);
				}
			};
			
			template.deserialize(fileName);
			
			this.idToReport.put(report.getId(), report);
			idToFileLastModified.put(report.getId(), lastModified);
			
			return report;
		}
	}

	private String getReportFileName(final String reportId) {
		return this.reportsDirectory.getPath() + "/" + reportId + ".xml";
	}
	
	public void serializeClasses() {
		SerializationTemplate template = new SerializationTemplate() {
			@Override
			protected Element buildRootElement(Document document) {
				return buildRootElementForClasses(document);
			}
		};
		
		template.serializeToXml(getClassesFileName());
	}
	
	public void deserializeClasses() {
		if(classesLastModified != null && classesLastModified.longValue() == classesFile.lastModified()){
			// classes already deserialized and the classes file has not been modified since then.
			//System.out.println("Classes already deserialized...");
			return;
		}
		System.out.println("edu.pw.treegrid.server.service.XMLMarshaller: Deserializing classes...");
		
		idToClass.clear();
		idToAttribute.clear();
		idToReport.clear();
		idToFileLastModified.clear();
		
		classesLastModified = classesFile.lastModified();
		
		DeserializationTemplate template = new DeserializationTemplate() {
			
			@Override
			protected void deserializeFromXML(Document document) {
				deserializeClassesFromXML(document);
			}
		};
		
		template.deserialize(getClassesFileName());
	}

	private String getClassesFileName() {
		return this.classesFile.getPath();
	}
	
	public Element buildRootElementForClasses(Document document) {
		Element classesElement = document.createElement(CLASSES_ELEMENT);
		for(DomainClass dc : classes) {
			classesElement.appendChild(dc.serializeToXML(document));
		}
		return classesElement;
	}
	
	public void deserializeClassesFromXML(Document document) {
		Element classesElement = document.getDocumentElement();
		if(this.classes == null){
			this.classes = new LinkedList<DomainClass>();
		}
		if(this.attributes == null){
			this.attributes = new LinkedList<DomainAttribute>();
		}
		this.classes.clear();
		this.attributes.clear();
		
		NodeList classesList = classesElement.getElementsByTagName(DomainClass.CLASS_ELEMENT);
		for(int i=0;i<classesList.getLength();++i) {
			Element e = (Element)classesList.item(i);
			DomainClass dc = new DomainClass();
			dc.deserializeFromXML(e);
			classes.add(dc);
		}
		
		computeIndices();
		setReferences();
	}
	
	
	private void setReferences() {
		for(DomainClass dc : classes) {
			dc.setReferences(this);
			for(DomainAttribute da : dc.getAttributes()) {
				da.setReferences(this);
			}
		}		
	}

	private void computeIndices() {
		for(DomainClass dc : classes) {
			this.idToClass.put(dc.getId(), dc);
			for(DomainAttribute da : dc.getAttributes()) {
				this.attributes.add(da);
				this.idToAttribute.put(da.getId(), da);
			}
		}		
	}
}